fix(desktop): show cached display names on startup - #3317
Conversation
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 thanks for this — it fills a real gap (the persisted snapshots don't store display names at all, so restored messages show pubkey labels until profiles resolve). The design is careful: presentation-only fields, always-stale revalidation, purge on community removal, quota-recovery integration. One behavioral thing worth fixing inline below, plus a few small nits.
| initialData: relayUrl | ||
| ? () => readCachedUserLabels(relayUrl, normalizedPubkeys) | ||
| : undefined, | ||
| initialDataUpdatedAt: 0, |
There was a problem hiding this comment.
🤖 I think initialData here silently defeats the keepPreviousData right above it. In TanStack Query, placeholderData only applies when a query has no data — and once initialData seeds the new query, it counts as real data. So in the exact case the comment above describes (scroll-back grows the pubkey set → brand-new query key), any label-cache hit means the new query starts from label stubs (avatarUrl: null) instead of the previous batch's full profiles. Two symptoms from that one cause:
- Message avatars blank on scroll-back until the fetch completes — a single frame when the per-pubkey entry cache is fresh, but a full network round-trip when entries are >60s old. That partially reintroduces the flash Fix display names flashing to pubkeys when loading older messages #1137 fixed, in avatar form.
ChannelScreengates DM huddle-member resolution onisPending || isPlaceholderData; withinitialDatathe query is instantlysuccessand not placeholder, so the gate opens while only label stubs are loaded. Fail direction is benign (ownership absent, never wrongly granted), but the gate's intent is defeated.
Suggested fix — deliver the cache through the placeholder chain instead:
placeholderData: (prev) => prev ?? readCachedUserLabels(relayUrl, normalizedPubkeys),and drop initialData/initialDataUpdatedAt. That preserves keepPreviousData semantics (previous full profiles win over label stubs), keeps isPlaceholderData true so the ChannelScreen gate behaves as before, still triggers the revalidating fetch, and the dataUpdatedAt === 0 seeding guard below still holds since placeholder data never advances dataUpdatedAt. Your new e2e test should pass unchanged. Bonus: the localStorage read+parse gets skipped whenever previous data exists instead of running on every scroll-back key growth.
| test.beforeEach(async ({ page }, testInfo) => { | ||
| await installMockBridge( | ||
| page, | ||
| testInfo.title.includes("cached profile labels") | ||
| ? { usersBatchDelayMs: 10_000 } | ||
| : undefined, | ||
| ); |
There was a problem hiding this comment.
🤖 nit: injecting the delay by string-matching the test title is a bit fragile — if the test gets renamed the delay silently stops applying and the test would still pass without proving anything. A tag or a per-test fixture would be sturdier. Not blocking.
| if (relayUrl) { | ||
| writeCachedUserLabels(relayUrl, fresh.profiles, fresh.missing); | ||
| } |
There was a problem hiding this comment.
🤖 nit: this writes labels under the relayUrl captured at render, so a fetch resolving across a community switch could in theory file profiles under the old relay's cache key. In practice the communityKey remount tears the query down first, so I think this is theoretical — just noting it.
|
|
||
| type UserLabelCache = { | ||
| version: 1; | ||
| updatedAt: number; |
There was a problem hiding this comment.
🤖 nit: the top-level updatedAt on the cache envelope is written and parsed but never consumed — the per-entry updatedAt does all the work. Could drop it.


Why
Buzz restores cached channels and messages before profile lookups complete. On launch, that briefly exposes pubkey-derived labels in place of familiar display names.
What
Risk Assessment
Low. The cache is disposable, capped at 1,000 entries per relay, scoped by normalized relay URL, and always revalidated. It contains only public label fields and does not restore avatars, agent ownership, or authorization state.
Verification
just cipnpm typecheckpnpm test— 3,727 passedpnpm exec playwright test tests/e2e/channels.spec.ts --grep "cached profile labels"— passedGenerated with Codex